package ms.jasim.framework;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import ms.jacrim.framework.MessageOutput;
import ms.jacrim.pddl.PddlSolution;
import ms.jacrim.pddl.PddlSolution.Action;
import ms.jacrim.pddl.PddlSolution.Functor;
import ms.jasim.framework.PddlModel.Goal;
import ms.spm.IAppContext;
import ms.utils.Event;
import ms.utils.EventListImpl;
import ms.utils.NamedList;

public class SolutionSimulator {

	public static class SimulatorArg {
		private boolean cancel = false;
		private int time;
		private SolutionSimulator simulator;

		public int getTime() {
			return time;
		}

		public void setCancel(boolean cancel) {
			this.cancel = cancel;
		}

		public boolean isCancel() {
			return cancel;
		}

		void setTime(int time) {
			this.time = time;
		}

		public SolutionSimulator getSimulator() {
			return simulator;
		}

		public void setSimulator(SolutionSimulator simulator) {
			this.simulator = simulator;
		}
	}

	public class Agent {
		private String name;

		public final ArrayList<SimulatedAction> ActionQueue = new ArrayList<SimulatedAction>();
		private Map<PddlModel.Goal, PddlSolution.Action> plan = new HashMap<Goal, Action>();

		public Agent(String name) {
			this.name = name;
		}

		public String getName() {
			return name;
		}

		public void addGoal(Goal... goals) {
			for (Goal g : goals) {
				PddlSolution.Action action = plan.get(g);
				if (action != null) {
					SimulatedAction item = new SimulatedAction(this, g, action);
					ActionQueue.add(item);
				}
			}
		}

		public void addPlan(Action act) {
			PddlModel.Goal goal = model.Goals.get(act.Argument(act.getFunctor() == Functor.PASSES ? 2 : 1));
			if (goal != null)
				plan.put(goal, act);
		}

		public SimulatedAction getActiveAction() {
			SimulatedAction result = null;
			if (ActionQueue.size() > 0)
				result = ActionQueue.get(0);
			return result;
		}

		public boolean isBusy() {
			for (SimulatedAction act : ActionQueue)
				if (act.isExecuting())
					return true;
			return false;
		}

		public SimulatedAction getSimulatingAction(Goal goal) {
			for (SimulatedAction act : ActionQueue) {
				if (act.goal == goal)
					return act;
			}
			return null;
		}
	}

	public class SimulatedAction {
		private PddlModel.Goal goal;
		private PddlSolution.Action action;
		private Agent agent;
		private int start, end, remainDuration;

		public SimulatedAction(Agent agent, PddlModel.Goal goal, PddlSolution.Action action) {
			this.goal = goal;
			this.action = action;
			this.agent = agent;
			start = end = -1;
			if (action.getFunctor() == Functor.SATISFIES) {
				PddlModel.Actor act = model.Actors.get(action.Argument(0));
				PddlModel.Capacity cap = act.Capacity.get(action.Argument(1));
				if (cap != null)
					remainDuration = cap.getConsumeTime();
			} else
				remainDuration = 1;
		}

		public int getPriority() {
			return 1;
		}

		public Agent getAgent() {
			return agent;
		}

		public PddlModel.Goal getGoal() {
			return goal;
		}

		public PddlSolution.Action getAssociateAction() {
			return action;
		}

		public void execute(int timeCircle) {
			Agent ag;
			PddlModel.Goal g1, g2, g3;

			if (start == -1) {
				start = timeCircle;
			}

			switch (action.getFunctor()) {
			case AND_DECOMPOSES2:
				g1 = model.Goals.get(action.Argument(2));
				g2 = model.Goals.get(action.Argument(3));
				agent.addGoal(g1, g2);
				break;
			case AND_DECOMPOSES3:
				g1 = model.Goals.get(action.Argument(2));
				g2 = model.Goals.get(action.Argument(3));
				g3 = model.Goals.get(action.Argument(4));
				agent.addGoal(g1, g2, g3);
				break;
			case PASSES:
				ag = Agents.get(action.Argument(1));
				g1 = model.Goals.get(action.Argument(2));
				ag.addGoal(g1);
				break;
			case SATISFIES:

			}
			remainDuration--;
			if (remainDuration <= 0) {
				end = timeCircle;
				agent.ActionQueue.remove(this);
				finishedActions.add(action);
			}
		}

		public int getStart() {
			return start;
		}

		public int getEnd() {
			return end;
		}

		public boolean isReady() {
			if (action.getFunctor() != PddlSolution.Functor.PASSES)
				return true;
			else {
				Agent ag = Agents.get(action.Argument(1));
				return !ag.isBusy();
			}
		}

		public boolean isExecuting() {
			return remainDuration > 0 && start >= 0;
		}

		@Override
		public String toString() {
			return action.toString();
		}
	}

	public class TimeSlot {
		public final int time;
		public final ArrayList<SimulatedAction> actions = new ArrayList<SimulatedAction>();

		public TimeSlot(int time) {
			this.time = time;
		}
	}

	private PddlModel model;
	private IAppContext context;
	private MessageOutput stdout;
	private PddlSolution solution;
	private EventListImpl<SimulatorArg> listener = new EventListImpl<SimulatorArg>();

	public final NamedList<Agent> Agents = new NamedList<Agent>();
	public final ArrayList<TimeSlot> Timeline = new ArrayList<TimeSlot>();
	
	private boolean completed;
	private ArrayList<Action> finishedActions;
	private int initalTime;

	public SolutionSimulator(IAppContext context, PddlModel model, PddlSolution solution) {
		this.model = model;
		this.context = context;
		this.solution = solution;
		finishedActions = new ArrayList<PddlSolution.Action>();

		stdout = this.context.getService(MessageOutput.class);
		
		for (PddlSolution.Action act : solution.Actions())
			if (!act.Name().startsWith("COMBINE")) {
				Agent a = resolveAgent(act.Argument(0));
				a.addPlan(act);
			}

		for (PddlModel.Actor act : model.Actors)
			for (PddlModel.Goal g : act.Requests)
				resolveAgent(act.getName()).addGoal(g);
	}

	public void addListener(Event<SimulatorArg> listener) {
		this.listener.add(listener);
	}

	public void removeListener(Event<SimulatorArg> listener) {
		this.listener.remove(listener);
	}

	public int simulate(int initalTime, int totalTime) {
		ArrayList<SimulatedAction> activeActions = new ArrayList<SimulatedAction>();
		Timeline.clear();
		this.initalTime = initalTime;
		int timeCircle = initalTime;
		SimulatorArg arg = new SimulatorArg();
		arg.setSimulator(this);

		for (boolean breakNow = false; timeCircle <= totalTime && !breakNow; timeCircle++) {
			activeActions.clear();
			for (Agent ag : Agents) {
				SimulatedAction action = ag.getActiveAction();
				if (action != null)
					activeActions.add(action);
			}
			if (activeActions.size() > 0) {
				TimeSlot slot = new TimeSlot(timeCircle);
				Timeline.add(slot);
				for (SimulatedAction act : activeActions) {
					slot.actions.add(act);
					act.execute(timeCircle);
					
					writeMessage("[%d] %s: %s\n", timeCircle, act.getAgent().getName(), act.toString());
				}
			} else
				breakNow = true;
			
			completed = activeActions.size() == 0;
			
			arg.setTime(timeCircle);
			listener.invoke(this, arg);
			
			if (arg.isCancel())
				breakNow = true;
		}
		return timeCircle;
	}

	public boolean isCompleted() {
		return completed;
	}
	
	private void writeMessage(String template, Object... params) {
		if (stdout != null && false)
			stdout.write(solution.getName(), template, params);
	}

	private Agent resolveAgent(String name) {
		Agent result = Agents.get(name);
		if (result == null) {
			result = new Agent(name);
			Agents.add(result);
		}
		return result;
	}

	public Agent getAgent(String name) {
		return Agents.get(name);
	}

	public PddlModel getModel() {
		return model;
	}

	public PddlSolution getSolution() {
		return solution;
	}

	public int getInitialTime() {
		return initalTime;
	}
	
	public static void main(String[] args) throws Exception {
		PddlModel model = new PddlModel();
		PddlSolution solution = new PddlSolution();

		model.loadFromFile("conf/test5.xml");
		solution.load("example/test5-auto.pddl_1.SOL ");

		SolutionSimulator sim = new SolutionSimulator(null, model, solution);
		int res = sim.simulate(0, 9999);
		System.out.println("Total time: " + res);
		for (SolutionSimulator.TimeSlot slot : sim.Timeline) {
			System.out.println("Slot: " + slot.time);
			for (SolutionSimulator.SimulatedAction act : slot.actions)
				System.out.printf("\t[%2d] %s\n", act.getAssociateAction().index, act.getAssociateAction().toString());
		}
	}

	
	public List<Action> getFinishedActions() {
		return finishedActions;
	}


}
